Tcl Source Code

Check-in [b569739692]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:[02977e0004] Reduce impact of recursion depth bug.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | core-8-6-branch
Files: files | file ages | folders
SHA3-256: b56973969223f1e2c57319ef270ade92164c4ad7bec67f750301c54b6be55d71
User & Date: dkf 2024-11-01 15:51:41.055
References
2024-11-12
13:03
backout [b569739692]: [02977e0004] Reduce impact of recursion depth bug. check-in: a72e9b170e user: jan.nijtmans tags: core-8-6-branch
12:10
backout [b569739692]: [02977e0004] Reduce impact of recursion depth bug. Remove some unneeded typeca... check-in: e1e7f93e74 user: jan.nijtmans tags: core-8-branch
09:41
backout [b569739692]: [02977e0004] Reduce impact of recursion depth bug. check-in: 53462a5b29 user: jan.nijtmans tags: trunk, main
2024-11-10
22:48
backout [b569739692]: [02977e0004] Reduce impact of recursion depth bug. Closed-Leaf check-in: 1008c8506a user: jan.nijtmans tags: bug-02977e0004
Context
2024-11-10
22:48
backout [b569739692]: [02977e0004] Reduce impact of recursion depth bug. Closed-Leaf check-in: 1008c8506a user: jan.nijtmans tags: bug-02977e0004
2024-11-01
20:56
fix logic error of test oo-1.24 (deep nested ownership for class) - class destroy doesn't invoke des... check-in: e8755dc826 user: sebres tags: core-8-6-branch
19:32
another variant to fix [02977e0004] (inclusive class deletion) using NRE-callout, without to extend ... check-in: 8d462098e4 user: sebres tags: bug-02977e0004-sebres
15:56
merge 8.6 check-in: a56b3c5c17 user: dkf tags: core-8-branch
15:51
[02977e0004] Reduce impact of recursion depth bug. check-in: b569739692 user: dkf tags: core-8-6-branch
12:01
There still is a buggy case to worry about Closed-Leaf check-in: 9378c3e912 user: dkf tags: bug-02977e0004
2024-10-31
13:53
merge 8.5 check-in: b58ad20346 user: sebres tags: core-8-6-branch
Changes
Unified Diff Ignore Whitespace Patch
Changes to generic/tclNamesp.c.
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
 */

void
Tcl_DeleteNamespace(
    Tcl_Namespace *namespacePtr)/* Points to the namespace to delete. */
{
    Namespace *nsPtr = (Namespace *) namespacePtr;
    Interp *iPtr = (Interp *) nsPtr->interp;
    Namespace *globalNsPtr = (Namespace *)
	    TclGetGlobalNamespace((Tcl_Interp *) iPtr);
    Tcl_HashEntry *entryPtr;
    Tcl_HashSearch search;
    Command *cmdPtr;

    /*
     * Ensure that this namespace doesn't get deallocated in the meantime.
     */







|
|
<







884
885
886
887
888
889
890
891
892

893
894
895
896
897
898
899
 */

void
Tcl_DeleteNamespace(
    Tcl_Namespace *namespacePtr)/* Points to the namespace to delete. */
{
    Namespace *nsPtr = (Namespace *) namespacePtr;
    Tcl_Interp *interp = nsPtr->interp;
    Namespace *globalNsPtr = (Namespace *) TclGetGlobalNamespace(interp);

    Tcl_HashEntry *entryPtr;
    Tcl_HashSearch search;
    Command *cmdPtr;

    /*
     * Ensure that this namespace doesn't get deallocated in the meantime.
     */
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
     * separate list of coros.
     */

    for (entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search);
	    entryPtr != NULL;) {
	cmdPtr = (Command *)Tcl_GetHashValue(entryPtr);
	if (cmdPtr->nreProc == TclNRInterpCoroutine) {
	    Tcl_DeleteCommandFromToken((Tcl_Interp *) iPtr,
		    (Tcl_Command) cmdPtr);
	    entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search);
	} else {
	    entryPtr = Tcl_NextHashEntry(&search);
	}
    }

    /*







|
<







929
930
931
932
933
934
935
936

937
938
939
940
941
942
943
     * separate list of coros.
     */

    for (entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search);
	    entryPtr != NULL;) {
	cmdPtr = (Command *)Tcl_GetHashValue(entryPtr);
	if (cmdPtr->nreProc == TclNRInterpCoroutine) {
	    Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr);

	    entryPtr = Tcl_FirstHashEntry(&nsPtr->cmdTable, &search);
	} else {
	    entryPtr = Tcl_NextHashEntry(&search);
	}
    }

    /*
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968

	/*
	 * Splice out and link to indicate that we've already been killed.
	 */

	nsPtr->ensembles = (Tcl_Ensemble *) ensemblePtr->next;
	ensemblePtr->next = ensemblePtr;
	Tcl_DeleteCommandFromToken(nsPtr->interp, ensemblePtr->token);
    }

    /*
     * If the namespace has a registered unknown handler (TIP 181), then free
     * it here.
     */








|







952
953
954
955
956
957
958
959
960
961
962
963
964
965
966

	/*
	 * Splice out and link to indicate that we've already been killed.
	 */

	nsPtr->ensembles = (Tcl_Ensemble *) ensemblePtr->next;
	ensemblePtr->next = ensemblePtr;
	Tcl_DeleteCommandFromToken(interp, ensemblePtr->token);
    }

    /*
     * If the namespace has a registered unknown handler (TIP 181), then free
     * it here.
     */

1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
	 * being deleted, ignore any second call.
	 */

	nsPtr->flags |= (NS_DYING|NS_KILLED);

	TclTeardownNamespace(nsPtr);

	if ((nsPtr != globalNsPtr) || (iPtr->flags & DELETED)) {
	    /*
	     * If this is the global namespace, then it may have residual
	     * "errorInfo" and "errorCode" variables for errors that occurred
	     * while it was being torn down. Try to clear the variable list
	     * one last time.
	     */








|







1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
	 * being deleted, ignore any second call.
	 */

	nsPtr->flags |= (NS_DYING|NS_KILLED);

	TclTeardownNamespace(nsPtr);

	if ((nsPtr != globalNsPtr) || (((Interp *) interp)->flags & DELETED)) {
	    /*
	     * If this is the global namespace, then it may have residual
	     * "errorInfo" and "errorCode" variables for errors that occurred
	     * while it was being torn down. Try to clear the variable list
	     * one last time.
	     */

1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050

	    nsPtr ->flags |= NS_DEAD;
	} else {
	    /*
	     * Restore the ::errorInfo and ::errorCode traces.
	     */

	    EstablishErrorInfoTraces(NULL, nsPtr->interp, NULL, NULL, 0);
	    EstablishErrorCodeTraces(NULL, nsPtr->interp, NULL, NULL, 0);

	    /*
	     * We didn't really kill it, so remove the KILLED marks, so it can
	     * get killed later, avoiding mem leaks.
	     */

	    nsPtr->flags &= ~(NS_DYING|NS_KILLED);







|
|







1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048

	    nsPtr ->flags |= NS_DEAD;
	} else {
	    /*
	     * Restore the ::errorInfo and ::errorCode traces.
	     */

	    EstablishErrorInfoTraces(NULL, interp, NULL, NULL, 0);
	    EstablishErrorCodeTraces(NULL, interp, NULL, NULL, 0);

	    /*
	     * We didn't really kill it, so remove the KILLED marks, so it can
	     * get killed later, avoiding mem leaks.
	     */

	    nsPtr->flags &= ~(NS_DYING|NS_KILLED);
Changes to generic/tclOO.c.
846
847
848
849
850
851
852











853













854









855
856

857
858
859
860
861
862
863

    /*
     * The namespace is only deleted if it hasn't already been deleted. [Bug
     * 2950259].
     */

    if (!Destructing(oPtr)) {











	Tcl_DeleteNamespace(oPtr->namespacePtr);













    }









    oPtr->command = NULL;
    TclOODecrRefCount(oPtr);

    return;
}

/*
 * ----------------------------------------------------------------------
 *
 * TclOODeleteDescendants --







>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
|
|
>







846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897

    /*
     * The namespace is only deleted if it hasn't already been deleted. [Bug
     * 2950259].
     */

    if (!Destructing(oPtr)) {
	/*
	 * Ensure that we don't recurse very deeply and blow out the C stack.
	 * [Bug 02977e0004]
	 */
	ThreadLocalData *tsdPtr = GetFoundation(interp)->tsdPtr;
	if (oPtr->classPtr) {
	    /*
	     * Classes currently need the recursion to get destructor calling
	     * right. This is a bug, but it requires a major rewrite of things
	     * to fix. 
	     */
	    Tcl_DeleteNamespace(oPtr->namespacePtr);
	    oPtr->command = NULL;
	    TclOODecrRefCount(oPtr);
	} else if (!tsdPtr->delQueueTail) {
	    /*
	     * Process a queue of objects to delete.
	     */
	    Object *currPtr, *tmp;
	    tsdPtr->delQueueTail = oPtr;
	    for (currPtr = oPtr; currPtr; currPtr = tmp) {
		Tcl_DeleteNamespace(currPtr->namespacePtr);
		currPtr->command = NULL;
		tmp = currPtr->delNext;
		TclOODecrRefCount(currPtr);
	    }
	    tsdPtr->delQueueTail = NULL;
	} else {
	    /*
	     * Enqueue the object.
	     */
	    tsdPtr->delQueueTail->delNext = oPtr;
	    tsdPtr->delQueueTail = oPtr;
	}
    } else {
	oPtr->command = NULL;
	TclOODecrRefCount(oPtr);
    }
    return;
}

/*
 * ----------------------------------------------------------------------
 *
 * TclOODeleteDescendants --
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
{
    Object *oPtr = clientData;
    Foundation *fPtr = oPtr->fPtr;
    FOREACH_HASH_DECLS;
    Class *mixinPtr;
    Method *mPtr;
    Tcl_Obj *filterObj, *variableObj;
    Tcl_Interp *interp = oPtr->fPtr->interp;
    int i;

    if (Destructing(oPtr)) {
	/*
	 * TODO:  Can ObjectNamespaceDeleted ever be called twice?  If not,
	 * this guard could be removed.
	 */







|







1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
{
    Object *oPtr = clientData;
    Foundation *fPtr = oPtr->fPtr;
    FOREACH_HASH_DECLS;
    Class *mixinPtr;
    Method *mPtr;
    Tcl_Obj *filterObj, *variableObj;
    Tcl_Interp *interp = fPtr->interp;
    int i;

    if (Destructing(oPtr)) {
	/*
	 * TODO:  Can ObjectNamespaceDeleted ever be called twice?  If not,
	 * this guard could be removed.
	 */
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146



1147
1148
1149
1150
1151
1152
1153
     * in that case when the destructor is partially deleted before the uses
     * of it have gone. [Bug 2949397]
     */

    if (!Tcl_InterpDeleted(interp) && !(oPtr->flags & DESTRUCTOR_CALLED)) {
	CallContext *contextPtr =
		TclOOGetCallContext(oPtr, NULL, DESTRUCTOR, NULL);
	int result;
	Tcl_InterpState state;

	oPtr->flags |= DESTRUCTOR_CALLED;

	if (contextPtr != NULL) {



	    contextPtr->callPtr->flags |= DESTRUCTOR;
	    contextPtr->skip = 0;
	    state = Tcl_SaveInterpState(interp, TCL_OK);
	    result = Tcl_NRCallObjProc(interp, TclOOInvokeContext,
		    contextPtr, 0, NULL);
	    if (result != TCL_OK) {
		Tcl_BackgroundException(interp, result);







<
<


<

>
>
>







1168
1169
1170
1171
1172
1173
1174


1175
1176

1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
     * in that case when the destructor is partially deleted before the uses
     * of it have gone. [Bug 2949397]
     */

    if (!Tcl_InterpDeleted(interp) && !(oPtr->flags & DESTRUCTOR_CALLED)) {
	CallContext *contextPtr =
		TclOOGetCallContext(oPtr, NULL, DESTRUCTOR, NULL);



	oPtr->flags |= DESTRUCTOR_CALLED;

	if (contextPtr != NULL) {
	    int result;
	    Tcl_InterpState state;

	    contextPtr->callPtr->flags |= DESTRUCTOR;
	    contextPtr->skip = 0;
	    state = Tcl_SaveInterpState(interp, TCL_OK);
	    result = Tcl_NRCallObjProc(interp, TclOOInvokeContext,
		    contextPtr, 0, NULL);
	    if (result != TCL_OK) {
		Tcl_BackgroundException(interp, result);
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
	 */
    } else {
	/*
	 * The namespace must have been deleted directly.  Delete the command
	 * as well.
	 */

	Tcl_DeleteCommandFromToken(oPtr->fPtr->interp, oPtr->command);
    }

    if (oPtr->myCommand) {
	Tcl_DeleteCommandFromToken(oPtr->fPtr->interp, oPtr->myCommand);
    }

    /*
     * Splice the object out of its context. After this, we must *not* call
     * methods on the object.
     */








|



|







1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
	 */
    } else {
	/*
	 * The namespace must have been deleted directly.  Delete the command
	 * as well.
	 */

	Tcl_DeleteCommandFromToken(interp, oPtr->command);
    }

    if (oPtr->myCommand) {
	Tcl_DeleteCommandFromToken(interp, oPtr->myCommand);
    }

    /*
     * Splice the object out of its context. After this, we must *not* call
     * methods on the object.
     */

1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
     * class of classes now as well. Due to the incestuous nature of those two
     * classes, if one goes the other must too and yet the tangle can
     * sometimes not go away automatically; we force it here. [Bug 2962664]
     */

    if (IsRootObject(oPtr) && !Destructing(fPtr->classCls->thisPtr)
	    && !Tcl_InterpDeleted(interp)) {

	Tcl_DeleteCommandFromToken(interp, fPtr->classCls->thisPtr->command);
    }

    if (oPtr->classPtr != NULL) {
	TclOOReleaseClassContents(interp, oPtr);
    }








<







1284
1285
1286
1287
1288
1289
1290

1291
1292
1293
1294
1295
1296
1297
     * class of classes now as well. Due to the incestuous nature of those two
     * classes, if one goes the other must too and yet the tangle can
     * sometimes not go away automatically; we force it here. [Bug 2962664]
     */

    if (IsRootObject(oPtr) && !Destructing(fPtr->classCls->thisPtr)
	    && !Tcl_InterpDeleted(interp)) {

	Tcl_DeleteCommandFromToken(interp, fPtr->classCls->thisPtr->command);
    }

    if (oPtr->classPtr != NULL) {
	TclOOReleaseClassContents(interp, oPtr);
    }

Changes to generic/tclOOInt.h.
193
194
195
196
197
198
199


200
201
202
203
204
205
206
    Tcl_Obj *cachedNameObj;	/* Cache of the name of the object. */
    Tcl_HashTable *chainCache;	/* Place to keep unused contexts. This table
				 * is indexed by method name as Tcl_Obj. */
    Tcl_ObjectMapMethodNameProc *mapMethodNameProc;
				/* Function to allow remapping of method
				 * names. For itcl-ng. */
    LIST_STATIC(Tcl_Obj *) variables;


} Object;

#define OBJECT_DESTRUCTING	1	/* Indicates that an object is being or has
								 *  been destroyed  */
#define DESTRUCTOR_CALLED 2	/* Indicates that evaluation of destructor script for the
							   object has began */
#define OO_UNUSED_4	4	/* No longer used.  */







>
>







193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
    Tcl_Obj *cachedNameObj;	/* Cache of the name of the object. */
    Tcl_HashTable *chainCache;	/* Place to keep unused contexts. This table
				 * is indexed by method name as Tcl_Obj. */
    Tcl_ObjectMapMethodNameProc *mapMethodNameProc;
				/* Function to allow remapping of method
				 * names. For itcl-ng. */
    LIST_STATIC(Tcl_Obj *) variables;
    struct Object *delNext;	/* If non-NULL, the next object to have its
				 * namespace deleted. */
} Object;

#define OBJECT_DESTRUCTING	1	/* Indicates that an object is being or has
								 *  been destroyed  */
#define DESTRUCTOR_CALLED 2	/* Indicates that evaluation of destructor script for the
							   object has began */
#define OO_UNUSED_4	4	/* No longer used.  */
289
290
291
292
293
294
295


296
297
298
299
300
301
302
typedef struct ThreadLocalData {
    int nsCount;		/* Epoch counter is used for keeping
				 * the values used in Tcl_Obj internal
				 * representations sane. Must be thread-local
				 * because Tcl_Objs can cross interpreter
				 * boundaries within a thread (objects don't
				 * generally cross threads). */


} ThreadLocalData;

typedef struct Foundation {
    Tcl_Interp *interp;
    Class *objectCls;		/* The root of the object system. */
    Class *classCls;		/* The class of all classes. */
    Tcl_Namespace *ooNs;	/* ::oo namespace. */







>
>







291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
typedef struct ThreadLocalData {
    int nsCount;		/* Epoch counter is used for keeping
				 * the values used in Tcl_Obj internal
				 * representations sane. Must be thread-local
				 * because Tcl_Objs can cross interpreter
				 * boundaries within a thread (objects don't
				 * generally cross threads). */
    Object *delQueueTail;	/* The tail object in the deletion queue. If
				 * NULL, there is no deletion queue. */
} ThreadLocalData;

typedef struct Foundation {
    Tcl_Interp *interp;
    Class *objectCls;		/* The root of the object system. */
    Class *classCls;		/* The class of all classes. */
    Tcl_Namespace *ooNs;	/* ::oo namespace. */
Changes to tests/oo.test.
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430






















431
432
433
434
435
436
437
	return $::deathOrder
    }} 5
} -cleanup {
    parent destroy
} -result {1 2 3 4 0}
test oo-1.23 {basic test of OO functionality: deep nested ownership} -setup {
    oo::class create parent
} -constraints knownBug -body {
    oo::class create abc {
	superclass parent
	method make {} {[self class] create xyz}
	destructor {incr ::count}
    }
    apply {n {
	set ::count 0
	# Make a lot of "nested" objects
	set base [abc new]






















	for {set i 1; set obj $base} {$i < $n} {incr i} {
	     set obj [$obj make]
	}
	# Kill them all in one go; should not crash!
	$base destroy
	return [expr {$n - $::count}]
    }} 10000







|









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
	return $::deathOrder
    }} 5
} -cleanup {
    parent destroy
} -result {1 2 3 4 0}
test oo-1.23 {basic test of OO functionality: deep nested ownership} -setup {
    oo::class create parent
} -body {
    oo::class create abc {
	superclass parent
	method make {} {[self class] create xyz}
	destructor {incr ::count}
    }
    apply {n {
	set ::count 0
	# Make a lot of "nested" objects
	set base [abc new]
	for {set i 1; set obj $base} {$i < $n} {incr i} {
	     set obj [$obj make]
	}
	# Kill them all in one go; should not crash!
	$base destroy
	return [expr {$n - $::count}]
    }} 10000
} -cleanup {
    parent destroy
} -result 0
test oo-1.24 {basic test of OO functionality: deep nested ownership} -setup {
    oo::class create parent
} -constraints knownBug -body {
    oo::class create abc {
	superclass parent
	self method make {} {oo::copy [self] xyz}
	destructor {incr ::count}
    }
    apply {n {
	set ::count 0
	# Make a lot of "nested" objects
	set base abc
	for {set i 1; set obj $base} {$i < $n} {incr i} {
	     set obj [$obj make]
	}
	# Kill them all in one go; should not crash!
	$base destroy
	return [expr {$n - $::count}]
    }} 10000